-module(ring).
-export([create/1,stop/1,init/1,test/0,
    ecrire/0,envoyer_ecrire/4,arreter/0,
    chercher/2,envoyer/3,afficherFingerTable/1,
    recuperer/2,calculStart/3,parcourir/1]).

bin_to_hex(Bin) when is_binary(Bin) ->
  lists:flatten([integer_to_list(X,16) || <<X>> <= Bin]).

lookup(CleHachee,{Nom,MaCle,Next,CleSuivant,FingerTable},NT) ->
if
    MaCle == CleHachee ->
    Next;
    MaCle < CleSuivant ->
    if 
        ((MaCle =< CleHachee ) and (CleHachee < CleSuivant)) ->
        Next;
        true ->
        {T,Noeud} = rechercheFingerTable(CleHachee,FingerTable),
        if
            T==trouve ->
            Noeud;
            true ->
            envoyer_ecrire(NT,Nom,'lookup',Next),
            Next ! {lookup,CleHachee,Nom,NT},
            receive
                {ok_lookup,Hote} ->
                Hote
            end
        end
    end;
    MaCle >= CleSuivant -> %dernier
    if
        (MaCle =< CleHachee) or (CleHachee <CleSuivant) ->
        Next;
        true ->
        if
            CleHachee >= CleSuivant ->
            {T,Noeud} = rechercheFingerTable(CleHachee,FingerTable),
            if
                T==trouve ->
                Noeud;
                true ->
                envoyer_ecrire(NT,Nom,'lookup',Next),
                Next ! {lookup,CleHachee,Nom,NT},
                receive
                    {ok_lookup,Hote} ->
                    Hote
                end
            end
        end
    end
end.


get(CleHachee,Who,INFOS,NT) ->
Hote = lookup(CleHachee,INFOS,NT),
Nom = element(1,INFOS),
envoyer_ecrire(NT,Nom,'getdata',Hote),
Hote ! {getdata,CleHachee,Who,NT}.

getData(Map,Cle)->
X=dict:find(Cle,Map),
if
    X==error->
    Data=non_trouve;
    true ->
    {_,Data}=X
end,
Data.

put(CleHachee,Data,Who,INFOS,NT) ->
Hote = lookup(CleHachee,INFOS,NT),
Nom = element(1,INFOS),
envoyer_ecrire(NT,Nom,'putdata',Hote),
Hote ! {putdata,CleHachee,Data,Who,NT}.

putData(Map,Cle,Data)->
Map2 = dict:store(Cle,Data,Map),
Map2.

relier(X,Y) ->
X ! {next,self(),1},
receive {NX,_} ->
    Y ! {next,self(),1},
    receive {NY,_} ->
        Y ! {set,NX,self(),1},
        X ! {set,NY,self(),1},
        receive {ok,_} -> ok end,
        receive {ok,_} -> ok end
    end
end.

wait(Next,Node,T) ->
{Cle,{Nom,Noeud,Map,FingerTable}} = Node,
CleSuivant = crypto:sha(term_to_binary(Next)),%io:format("wait ~w~n",[Nom]),
INFOS = {Nom,Cle,Next,CleSuivant,FingerTable},

receive
    {next,Who,TT} ->
    NT = max(T,TT)+1,
    Who ! {Next,NT},
    envoyer_ecrire(NT,Nom,'next',Who),
    wait(Next,Node,NT);

    {set,NNext,Who,TT} -> 
    NT = max(T,TT)+1,
    Who ! {ok,NT},
    envoyer_ecrire(NT,Nom,'set',Who),
    wait(NNext,Node,NT); 

    {nom, Who,TT} -> 
    NT = max(T,TT)+1,
    Who ! {Nom,NT},
    envoyer_ecrire(NT,Nom,'nom',Who),
    wait(Next,Node,NT);

    {lookup,CleHachee,Who,TT} ->
    NT = max(T,TT)+1,
    Hote = lookup(CleHachee,INFOS,NT),
    Who ! {ok_lookup,Hote},
    envoyer_ecrire(NT,Nom,'ok_lookup',Who),
    wait(Next,Node,NT);

    {get,CleHachee,Who,TT} -> 
    NT = max(T,TT)+1,
    get(CleHachee,Who,INFOS,NT),
    wait(Next,Node,NT);

    {getdata,CleHachee,Who,TT} -> 
    NT = max(T,TT)+1,
    Data = getData(Map,CleHachee),
    Who ! {ok_get,Data},
    envoyer_ecrire(NT,Nom,'ok_get',Who),
    wait(Next,Node,NT);

    {put,CleHachee,Data,Who,TT} ->
    NT = max(T,TT)+1,
    put(CleHachee,Data,Who,INFOS,NT),
    wait(Next,Node,NT);

    {putdata,CleHachee,Data,Who,TT} ->
    NT = max(T,TT)+1,
    Map2 = putData(Map,CleHachee,Data),
    Who ! {ok_put},
    envoyer_ecrire(NT,Nom,'ok_put',Who),
    NNode = {Cle,{Nom,Noeud,Map2,FingerTable}},
    wait(Next,NNode,NT);

    {cle,Who,TT} -> 
    NT = max(T,TT)+1,
    Who ! {Cle,NT},
    envoyer_ecrire(NT,Node,'cle',Who),
    wait(Next,Node,NT);  

    {cleSuivant, Who,TT} -> 
    NT = max(T,TT)+1,
    Who ! {CleSuivant,Nom,NT},
    envoyer_ecrire(NT,Nom,'cleSuivant',Who),
    wait(Next,Node,NT);

    {getliste,Liste,Who,TT} -> 
    NT = max(T,TT)+1,
    case lists:keymember(Nom, 1, Liste) of
        true -> 
        envoyer_ecrire(NT,Node,'ok_liste',Who),
        Who!{ok_liste,Liste};
        false ->
        Elements = lists:map(fun({CleData,Data})-> {bin_to_hex(CleData),Data} end, lists:keysort(1,dict:to_list(Map))),
        Fingers = lists:map(fun({_,_,Finger})-> Finger end, FingerTable),
        Liste2=Liste++[{Nom,Elements,Fingers}],
        envoyer_ecrire(NT,Nom,'getliste',Next),
        Next ! {getliste,Liste2,Who,NT}
    end,
    wait(Next,Node,NT);

    {fingertable,Who,TT} ->
    NT = max(T,TT)+1,
    if
        length(FingerTable)==0 ->
        M=160,
        FingerTable2 = creerFingerTable(Cle,64,M,FingerTable,INFOS,NT),
        %io:format("~w]~n",[Nom]),
        %afficherFingerTable(FingerTable2),
        NNode = {Cle,{Nom,Noeud,Map,FingerTable2}},
        envoyer_ecrire(NT,Nom,'fingertable',Next),
        Next ! {fingertable,Who,NT};
        true ->
        envoyer_ecrire(NT,Nom,'ok_fingertable',Who),
        Who ! ok_fingertable,
        NNode = Node
    end,
    wait(Next,NNode,NT);

    stop -> {}
end.

creerFingerTable(Cle,K,M,FingerTable,INFOS,NT) ->
if 
    K>=2 -> 
    N = binary:decode_unsigned(Cle,big),
    A = calculStart(N,K,M),
    B = calculStart(N,K/2,M),
    StartActuel  = binary:encode_unsigned(A,big),
    StartSuivant = binary:encode_unsigned(B,big),

    Interval = [StartActuel,StartSuivant],

    Node = lookup(StartActuel,INFOS,NT),
    Finger = [{StartActuel,Interval,Node}],
    Finger ++ creerFingerTable(Cle,K/2,M,FingerTable,INFOS,NT);
    true ->
    FingerTable
end.

calculStart(N,K,M) ->
DM = round(math:pow(2,M)),
A = (N+round((1/K)*DM)),

Start = A rem DM,
Start.


rechercheFingerTable(_,[]) ->
{nontrouve,nontrouve};
rechercheFingerTable(Cle,[Ligne|Reste]) ->
{T,Noeud} = rechercheFingerTableLigne(Cle,Ligne),
if
    T==trouve ->
    {trouve,Noeud};
    true->
    rechercheFingerTable(Cle,Reste)
end.

rechercheFingerTableLigne(Cle,{_,[Deb,Fin],Noeud}) ->

if
    (Deb=<Cle) and (Cle<Fin) ->
    {trouve,Noeud};
    true->
    {nontrouve,nontrouve}
end.

afficherFingerTable([])-> ok;
afficherFingerTable([{Start,Interval,Who}|Reste])->
io:format("S: ~w~n",[Start]),
io:format("I: ~w~n",[Interval]),
io:format("P:~w~n",[Who]),
afficherFingerTable(Reste).

envoyer_ecrire(Temps,Nom,Message,Dest) -> 
Cle = crypto:sha(term_to_binary(Nom)),
CleSuivant = crypto:sha(term_to_binary(Dest)),
ecrivain ! {Temps,{Nom,Cle},Message,{Dest,CleSuivant}}.

ecrire()->
receive
    {Temps,{Pr1,Hach1},Message,{Pr2,Hach2}} ->
    file:write_file("log.txt", io_lib:fwrite("~w) ~p) ~w]~n <~w,~w> ~n --~w--> ~n <~w,~w> ~n~n", 
        [date(),time(),Temps,Pr1,binary:decode_unsigned(Hach1,big),Message,Pr2,binary:decode_unsigned(Hach2,big)]),[append]),
    ecrire();
    stop_ecrire -> {}
end.


creer_ecrivain() ->
Pid = spawn(?MODULE,ecrire,[]),
file:write_file("log.txt", io_lib:fwrite("",[])),
register(ecrivain, Pid).

create(L) ->
creer_ecrivain(),
N = lists:map(fun(X)-> {crypto:sha(term_to_binary(X)),{X,node(),dict:new(),[]}} end, L),
NT = lists:keysort(1,N),
{_,[Tete|_]} = pop(NT),
NO = lists:map(fun({_,{Nom,Noeud,Map,FingerTable}}) -> {Nom,Noeud,Map,FingerTable} end, NT),


afficher(NO),
{Tete,NO}.

stop([]) -> 
ecrivain ! stop_ecrire;
stop([E|R]) ->
E ! stop,
stop(R).

init(Node) ->
{_,{Nom,_,_,_}} = Node,
wait(Nom,Node,0).

popNode(Node) ->
{_,{Nom,_,_,_}} = Node,
Pid = spawn(?MODULE,init,[Node]),
register(Nom, Pid),
{Nom,[Nom]}.

pop([Node|[]]) ->
{P,A} = popNode(Node),
{P,A};
pop([Node|R]) ->
{P,A} = popNode(Node),
{Q,B} = pop(R),
relier(P,Q),
{Q, A++B}
.

afficher({_,{Nom,Node,_,_}}) ->
io:format("Node{Nom(~w) Node(~w) Cle(...)} ~n",[Nom,Node]);
afficher({Nom,Node,_,_}) ->
io:format("Node{Nom(~w) Node(~w)} ~n",[Nom,Node]);
afficher([]) -> {};
afficher([Node|R]) ->
afficher(Node),
afficher(R).

test() ->
L = [toto,tata,bob,anna,lea],
{Tete,_}=create(L),
fingerTable(Tete),
envoyer(Tete,cle,abcdef),
recuperer(Tete,cle).

arreter() ->
stop([toto,tata,bob,anna,lea]).

chercher(Noeud,Nom) ->
Noeud ! {lookup,crypto:sha(term_to_binary(Nom)),self(),0},
receive
    {ok_lookup,Hote} ->
    Hote
end.

envoyer(Noeud,Cle,Data) ->
Noeud ! {put,crypto:sha(term_to_binary(Cle)),Data,self(),0},
receive
    {ok_put} ->
    ok
end.

recuperer(Noeud,Cle) ->
Noeud ! {get,crypto:sha(term_to_binary(Cle)),self(),0},
receive
    {ok_get,Data} ->
    Data
end.

parcourir(Noeud)->
Noeud ! {getliste,[],self(),0},
receive
  {ok_liste,ListeRecue} -> 
  afficherRing(ListeRecue),
  {server,'server@PC'} ! {self(), ListeRecue}, ok
end.

afficherRing([])->ok;
afficherRing([{Nom,Datas,Fingers}|Reste])->
io:format("~w]~n",[Nom]),
afficherRingDatas(Datas),
io:format("Fingers : ~w~n",[Fingers]),
io:format("~n"),
afficherRing(Reste).

afficherRingDatas([])->ok;
afficherRingDatas([{Cle,Data}|Reste])->
io:format("[~w]=>~n~w~n",[Cle,Data]),
afficherRingDatas(Reste).

fingerTable(Noeud) ->
Noeud ! {fingertable,self(),0},
receive
    ok_fingertable -> ok
end.